﻿/// Copyright © 2013 Microsoft Corporation. 
/// This project is released under the Microsoft Public License (MS-PL)
///
namespace DataMorph.Common.DataAccess
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;

    using DataMorph.Common.Entities;
    using DataMorph.Common.Storage;

    using Microsoft.Practices.Unity;
    using Microsoft.WindowsAzure.Storage;
    using Microsoft.WindowsAzure.Storage.Auth;
    using Microsoft.WindowsAzure.Storage.Table;

    public class DataAccess : IDataAccess
    {
        private readonly ICloudStorageAccount cloudStorageAccount;

        private readonly Func<string, string, ICloudStorageAccount> storageAccountFactory;

        private readonly CloudTableClient cloudTableClient;

        private readonly CloudTable dataSetsTable;

        private readonly CloudTable processingRecordsTable;

        private readonly CloudTable blobRecordTable;

        private readonly TableRequestOptions defaultTableRequestOptions = new TableRequestOptions
                                                                              {
                                                                                  RetryPolicy = DataMorphGlobalRetryPolicy.NewPolicy
                                                                              };

        [InjectionConstructor]
        public DataAccess(ICloudStorageAccount cloudStorageAccount)
            : this(
                cloudStorageAccount,
                (accountName, accountKey) =>
                new CloudStorageAccountWrapper(
                    new CloudStorageAccount(new StorageCredentials(accountName, accountKey), false)))
        {
        }

        public DataAccess(
            ICloudStorageAccount cloudStorageAccount, 
            Func<string, string, ICloudStorageAccount> storageAccountFactory)
        {
            if (cloudStorageAccount == null)
            {
                throw new ArgumentNullException("cloudStorageAccount", "Cloud storage account must not be null.");
            }

            this.cloudStorageAccount = cloudStorageAccount;
            this.storageAccountFactory = storageAccountFactory;
            this.cloudTableClient = this.cloudStorageAccount.CreateCloudTableClient();

            this.dataSetsTable = this.cloudTableClient.GetTableReference(TableNames.DataSetsTable);
            this.dataSetsTable.CreateIfNotExists();

            this.processingRecordsTable = this.cloudTableClient.GetTableReference(TableNames.TimingsTable);
            this.processingRecordsTable.CreateIfNotExists();

            this.blobRecordTable = this.cloudTableClient.GetTableReference(TableNames.BlobRecordTable);
            this.blobRecordTable.CreateIfNotExists();
        }

        public IEnumerable<StorageAccountDetailsModel> GetStorageAccountNames()
        {
            var dataSets = this.dataSetsTable.ExecuteQuery(new TableQuery<DataSetEntity>());

            return dataSets.Select(
                    d => new StorageAccountDetailsModel { Name = d.StorageAccountName, Key = d.StorageAccountKey })
                        .GroupBy(d => d.Name)
                        .Select(g => g.First());
        }

        public IEnumerable<DataSetEntity> GetDataSets(string accountName)
        {
            var filter = TableQuery.GenerateFilterCondition("StorageAccountName", QueryComparisons.Equal, accountName);
            var result = this.dataSetsTable.ExecuteQuery(new TableQuery<DataSetEntity>().Where(filter));
            return result;
        }

        public void AddDataSet(GenerationParameters generationParameters)
        {
            try
            {
                this.dataSetsTable.Execute(TableOperation.Insert(new DataSetEntity(generationParameters)), this.defaultTableRequestOptions);
            }
            catch (Exception e)
            {
                Trace.TraceError("Error while writing datsets table {0}", e);
            }
        }

        public ICloudStorageAccount GetStorageAccount(string accountName, string accountKey)
        {
            return this.storageAccountFactory(accountName, accountKey);
        }

        public void AddProcessingRecords(IEnumerable<ProcessingRecordEntity> processingRecordEntities)
        {
            foreach (var processingRecordEntity in processingRecordEntities)
            {
                processingRecordEntity.PartitionKey = GenerateProcessingPartitionKey(
                                                                        processingRecordEntity.DataSet, 
                                                                        processingRecordEntity.TimeRequested);

                processingRecordEntity.RowKey = string.Format(
                                                          "{0}_{1}_{2}_{3}",
                                                          processingRecordEntity.DataSet,
                                                          processingRecordEntity.TimeRequested.Ticks,
                                                          processingRecordEntity.DeploymentId,
                                                          processingRecordEntity.InstanceId);

                this.processingRecordsTable.Execute(TableOperation.InsertOrReplace(processingRecordEntity), this.defaultTableRequestOptions);
            }
        }

        public IEnumerable<ProcessingRecordEntity> GetProcessingRecordEntities(string storageAccount)
        {
            var filter = TableQuery.GenerateFilterCondition("StorageAccount", QueryComparisons.Equal, storageAccount);

            return this.processingRecordsTable.ExecuteQuery(new TableQuery<ProcessingRecordEntity>().Where(filter));
        }

        public void AddTagBlobRecord(
            string dataSetName,
            string timeRequested,
            string tagId,
            string deploymentId,
            string instanceId,
            int batchId,
            string blobUri)
        {
            var blobRecord = new TagBlobRecord
                                 {
                                     PartitionKey =
                                         string.Format("{0}-{1}-{2}", dataSetName, timeRequested, tagId),
                                     RowKey =
                                         string.Format("{0}-{1}-{2}-{3}", deploymentId, instanceId, batchId, DateTime.UtcNow.Ticks),
                                     BlobUri = blobUri
                                 };
            this.blobRecordTable.Execute(TableOperation.Insert(blobRecord), this.defaultTableRequestOptions);
        }

        private static string GenerateProcessingPartitionKey(string dataSetName, DateTimeOffset timeRequested)
        {
            return string.Format(
                "{0}_{1}",
                TextHelper.EscapeAzureTableKeyField(dataSetName),
                timeRequested.UtcDateTime.ToString("MMddyyyyHHmmss"));
        }
    }
}